#region License, Terms and Conditions
//
// Prawks - Copyright (c) 2007 Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Prawks.SqlMethods
{
    using System;
    using System.Data;
    using System.Data.SqlClient;
    using System.Data.SqlTypes;
    using Prawks.DbMethods;

    using Debug = System.Diagnostics.Debug;

    /// <summary>
    /// Base class for attributes that describe a <see cref="SqlParameter"/>. 
    /// </summary>
    
    [ AttributeUsage(AttributeTargets.Parameter) ]
    public abstract class SqlParameterAttributeBase : DbParameterAttribute
    {
        private SqlDbType _paramType;
        private bool _paramTypeDefined;

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class.
        /// </summary>

        protected SqlParameterAttributeBase() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name.
        /// </summary>

        protected SqlParameterAttributeBase(string name) : 
            base(name) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter size.
        /// </summary>

        protected SqlParameterAttributeBase(int size) : 
            base(size) {}
        
        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name and size.
        /// </summary>

        protected SqlParameterAttributeBase(string name, int size) : 
            base(name, size) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter type.
        /// </summary>

        protected SqlParameterAttributeBase(SqlDbType paramType)
        {
            SqlDbType = paramType;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name and type.
        /// </summary>

        protected SqlParameterAttributeBase(string name, SqlDbType paramType) : 
            base(name)
        {
            SqlDbType = paramType;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter type and size.
        /// </summary>

        protected SqlParameterAttributeBase(SqlDbType paramType, int size) : 
            base(size)
        {
            SqlDbType = paramType;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name, type and size.
        /// </summary>

        protected SqlParameterAttributeBase(string name, SqlDbType paramType, int size) 
            : base(name, size)
        {
            SqlDbType = paramType;
        }

        /// <summary>
        /// Gets or sets the <see cref="System.Data.SqlDbType"/> of the parameter.
        /// </summary>

        public SqlDbType SqlDbType
        {
            get 
            { 
                if (!_paramTypeDefined)
                    throw new InvalidOperationException("Value is undefined.");

                return _paramType; 
            }

            set 
            { 
                _paramType = value; 
                _paramTypeDefined = true;
            }
        }

        /// <summary>
        /// Indicates whether <see cref="SqlDbType"/> is defined or not.
        /// </summary>

        public bool IsSqlDbTypeDefined
        {
            get { return _paramTypeDefined; }
        }

        internal void ConfigureParameter(SqlParameter parameter, 
            string nameDisposition, Type typeDisposition, ParameterDirection directionDisposition)
        {
            Debug.Assert(parameter != null);
            Debug.Assert(nameDisposition != null);
            Debug.Assert(typeDisposition != null);
            Debug.Assert(directionDisposition != ParameterDirection.ReturnValue);

            base.ConfigureParameter(parameter, nameDisposition);
            
            //
            // Force the direction of the parameter to the one given by 
            // its disposition unless it is a ReturnValue parameter.
            //

            if (parameter.Direction != ParameterDirection.ReturnValue)
                parameter.Direction = directionDisposition;

            //
            // Infer the SqlDbType from the incoming source type if one was 
            // not specified on the attribute.
            //

            parameter.SqlDbType = IsSqlDbTypeDefined ?
                SqlDbType : InferSqlDbType(typeDisposition);
        }

        private static SqlDbType InferSqlDbType(Type type)
        {
            Debug.Assert(type != null);

            if (type == typeof(Int64)) return SqlDbType.BigInt;
            if (type == typeof(Byte[])) return SqlDbType.VarBinary;
            if (type == typeof(Boolean)) return SqlDbType.Bit;
            if (type == typeof(DateTime)) return SqlDbType.DateTime;
            if (type == typeof(Decimal)) return SqlDbType.Decimal;
            if (type == typeof(Double)) return SqlDbType.Float;
            if (type == typeof(Int32)) return SqlDbType.Int;
            if (type == typeof(String)) return SqlDbType.NVarChar;
            if (type == typeof(Single)) return SqlDbType.Real;
            if (type == typeof(Int16)) return SqlDbType.SmallInt;
            if (type == typeof(Byte)) return SqlDbType.TinyInt;
            if (type == typeof(Guid)) return SqlDbType.UniqueIdentifier;
            if (type == typeof(Object)) return SqlDbType.Variant;

            if (type == typeof(SqlInt64)) return SqlDbType.BigInt;
            if (type == typeof(SqlBinary)) return SqlDbType.VarBinary;
            if (type == typeof(SqlBoolean)) return SqlDbType.Bit;
            if (type == typeof(SqlDateTime)) return SqlDbType.DateTime;
            if (type == typeof(SqlDecimal)) return SqlDbType.Decimal;
            if (type == typeof(SqlDouble)) return SqlDbType.Float;
            if (type == typeof(SqlInt32)) return SqlDbType.Int;
            if (type == typeof(SqlMoney)) return SqlDbType.Money;
            if (type == typeof(SqlString)) return SqlDbType.NVarChar;
            if (type == typeof(SqlSingle)) return SqlDbType.Real;
            if (type == typeof(SqlInt16)) return SqlDbType.SmallInt;
            if (type == typeof(SqlByte)) return SqlDbType.TinyInt;
            if (type == typeof(SqlGuid)) return SqlDbType.UniqueIdentifier;

            /* TODO: Review if a more specific exception cannot be thrown here. NotSupportedException is way too general. */
            throw new NotSupportedException(string.Format("Cannot infer SQL type from {0}.", type.FullName));
        }
    }

    /// <summary>
    /// Indicates how a <see cref="SqlParameter"/> should be defined by
    /// <see cref="SqlMethod"/> or <see cref="SqlOutMethod"/>.
    /// This class cannot be inherited.
    /// </summary>
    
    [ AttributeUsage(AttributeTargets.Parameter) ]
    public sealed class SqlParameterAttribute : SqlParameterAttributeBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttribute"/> class.
        /// </summary>

        public SqlParameterAttribute() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name.
        /// </summary>

        public SqlParameterAttribute(string name) : 
            base(name) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter size.
        /// </summary>

        public SqlParameterAttribute(int size) : 
            base(size) {}
        
        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name and size.
        /// </summary>

        public SqlParameterAttribute(string name, int size) : 
            base(name, size) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter type.
        /// </summary>

        public SqlParameterAttribute(SqlDbType paramType) :
            base(paramType) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name and type.
        /// </summary>

        public SqlParameterAttribute(string name, SqlDbType paramType) : 
            base(name, paramType) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter type and size.
        /// </summary>
        
        public SqlParameterAttribute(SqlDbType paramType, int size) : 
            base(paramType, size) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlParameterAttributeBase"/> class
        /// with the parameter name, type and size.
        /// </summary>

        public SqlParameterAttribute(string name, SqlDbType paramType, int size) 
            : base(name, paramType, size) {}
    }

    /// <summary>
    /// Indicates a SQL Server <b>sysname</b> parameter type, which is defined as 
    /// <b>nvarchar(128)</b>. This class cannot be inherited.
    /// </summary>

    [ AttributeUsage(AttributeTargets.Parameter) ]
    public sealed class SqlSysNameAttribute : SqlParameterAttributeBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SqlSysNameAttribute"/> class.
        /// </summary>

        public SqlSysNameAttribute() : 
            base(SqlDbType.NVarChar, 128) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlSysNameAttribute"/> class
        /// with the parameter name.
        /// </summary>

        public SqlSysNameAttribute(string name) : 
            base(name, SqlDbType.NVarChar, 128) {}
    }

    /// <summary>
    /// Indicates a SQL Server <b>sysname</b> parameter type, which is defined as 
    /// <b>nvarchar(128)</b>. This class cannot be inherited.
    /// </summary>

    [ AttributeUsage(AttributeTargets.Parameter) ]
    public sealed class SqlReturnValueAttribute : SqlParameterAttributeBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SqlSysNameAttribute"/> class.
        /// </summary>

        public SqlReturnValueAttribute() : 
            base("RETURN_VALUE", SqlDbType.Int) 
        {
            Direction = ParameterDirection.ReturnValue;
        }
    }
}
